﻿/*
 * Cashless Controller 1.0
 * Timothy A. Grubb tgrubb@studioonemedia.com
 * 
 * 
 * 
 */

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO.Ports;


namespace Cashless
{

    // Messages from controller to kiosk
    public enum CashlessRecvMsg
    {
        R1_STANDBY = 1,
        R2_READY,
        R3_COLLECTED,
        R4_CARDDENIED,
        R5_VERSION,
        R6_IP_MAC,
        R7_NETWORK_ERROR,
        R8_EQUIPMENT_ERROR,
        R9_MISC
    };

    // Messages from kiosk to controller
    public enum CashlessSendMsg
    {
        M1_STATUS,
        M2_COLLECT,
        M3_RESET,
        M4_STOP,
        M5_VERSION,
        M6_IP_MAC,
        M7_MISC
    };


    public class CashlessControllerWeb
    {

        // Network Errors
        public const string NETWORK_NOERROR = "00";
        public const string NETWORK_NOLINK  = "01";
        public const string NETWORK_NOTRANS = "02";
        public const string NETWORK_NOAUTH  = "03";

        // Equipment Errors
        public const string EQUIPMENT_NOERROR   = "00";
        public const string EQUIPMENT_BUSY      = "01";
        public const string EQUIPMENT_FUNDSNR   = "02";
        public const string EQUIPMENT_BACOMM    = "10";
        public const string EQUIPMENT_BAGENERIC = "11";
        public const string EQUIPMENT_BAJAM     = "12";
        public const string EQUIPMENT_BAFULL    = "13";
        public const string EQUIPMENT_BANOVAULT = "14"; 


        SerialPort _sp;                             // Serial port to connect to controller
        public CashlessSendMsg _currentSend;        // last sent message from kiosk
        public CashlessRecvMsg _currentRecv;        // last received message from controller
        private string _currentControllerVersion;   // Current version of the controller
        public bool IsAlive = false;                // Is the controller active

        public System.Timers.Timer PollTimer;
        private int _pollInterval = 500;



        private bool _isCashActive = false;  // Current status of ability to take cash
        private bool _isCreditActive = false; // Current status of ability to take credit cards

        private int _paymentAmount = 0;
        private string _paymentTrans = "";
        private bool _continue = false;
        private bool _isProcessingPayment = false;


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="portname"></param>
        public CashlessControllerWeb( string portname)
        {

            try
            {
                // Connect to the Cashless Controller via the serial port
                _sp = new SerialPort(portname);
                _sp.NewLine = "\r";
                _sp.DataReceived += new SerialDataReceivedEventHandler(_sp_DataReceived);

                Connect();
            }
            catch (Exception e)
            {
                WriteToEventLog(e.Message, EventLogEntryType.Error);
            }

        }

        public void Connect()
        {
            // Connect to the Cashless Controller via the serial port
            try
            {
                _sp.Open();

                if (!_sp.IsOpen)
                {
                    IsAlive = false;
                    //OnControllerStateChange();
                    throw new Exception("Port Wont Open");
                }
                else
                {
                    IsAlive = true;
                }


                _sp.DataReceived += new SerialDataReceivedEventHandler(_sp_DataReceived);
            }
            catch (Exception e)
            {
                WriteToEventLog(e.Message, EventLogEntryType.Error);
            }
        }

        public int RequestPayment(int amount)
        {
            SendMessage(CashlessSendMsg.M2_COLLECT,amount + "");
            WriteToEventLog("RequestPayment():"+amount, EventLogEntryType.Information);
            StartStatusPolling();
            Disconnect();
            return _paymentAmount;
        }

        public void CancelPayment()
        {
            _paymentAmount = 0;
            _continue = false;
        }

        public void StartStatusPolling()
        {
            _paymentAmount = 0;
            _continue = true;

            while (_continue)
            {
                SendMessage(CashlessSendMsg.M1_STATUS);
                Thread.Sleep(150);
            }
            SendMessage(CashlessSendMsg.M4_STOP);
            _isProcessingPayment = false;
        }

        public void StopStatusPolling()
        {
            SendMessage(CashlessSendMsg.M4_STOP);
            Thread.Sleep(150);
            _continue = false;
        }

        public string PaymentTrans
        {
            get { return _paymentTrans; }
            set { _paymentTrans = value; }
        }

        /// <summary>
        /// Handler from data received from controller
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            String data = "";

            if (_sp.IsOpen & _sp != null)
            {
                try
                {
                    data = _sp.ReadLine();

                    if (data.Equals("R1_Standby"))
                    {
                        OnStandby();
                    }
                    else if (data.Equals("R2_Ready"))
                    {
                        OnReady();
                    }
                    else if (data.Equals("R3_$01.00Collected"))
                    {
                        SendMessage(CashlessSendMsg.M4_STOP);
                        OnPaid(100,"cash");
                    }
                    else if (data.Equals("R3_$05.00Collected"))
                    {
                        SendMessage(CashlessSendMsg.M4_STOP);
                        OnPaid(500,"cash");
                    }
                    else if (data.Equals("R3_$10.00Collected"))
                    {
                        SendMessage(CashlessSendMsg.M4_STOP);
                        OnPaid(1000,"cash");
                    }
                    else if (data.Equals("R3_$20.00Collected"))
                    {
                        SendMessage(CashlessSendMsg.M4_STOP);
                        if (!_isProcessingPayment)
                        {
                            WriteToEventLog("_sp_DataReceived(R3_$20.00Collected)", EventLogEntryType.Error);
                            OnPaid(2000, "cash");
                        }
                    }
                    else if (data.Equals("R4_CardDenied"))
                    {
                        OnDenied();
                    }
                    else if (data.Contains("R5_Version"))
                    {
                        string version = data.Replace("R5_Version", "");

                        // Check for controller version change
                        if (!String.IsNullOrEmpty(_currentControllerVersion))
                        {
                            if (!_currentControllerVersion.Equals(version))
                            {

                                WriteToEventLog("Controller version change from " +
                                                _currentControllerVersion + " to " + version,
                                                EventLogEntryType.Information);

                                // Update the current version
                                _currentControllerVersion = version;
                            }
                        }
                        else
                        {
                            _currentControllerVersion = version;
                        }

                        OnVersion(version);
                    }
                    else if (data.Contains("R6_"))
                    {
                        string tmp = data.Replace("R6_", "").Trim();
                        char[] sep = {'/'};
                        string[] vals = tmp.Split(sep);
                        OnIPMAC(vals);
                    }
                    else if (data.Contains("R7_NetworkError:"))
                    {
                        char[] sep = { ':' };
                        string[] d = data.Split(sep);
                        string errorCode = String.IsNullOrEmpty(d[1]) ? "" : d[1];

                        if (errorCode == NETWORK_NOERROR)
                        {
                            if (!IsCreditActive)
                            {
                                WriteToEventLog("Credit use has become active", EventLogEntryType.Information);
                                _isCreditActive = true;
                                OnPaymentStateChange();
                            }

                            //_isCreditActive = true;
                        }
                        else if (errorCode == NETWORK_NOAUTH)
                        {
                            if (IsCreditActive)
                            {
                                WriteToEventLog("Authorization server not responding", EventLogEntryType.FailureAudit);
                                _isCreditActive = false;
                                OnPaymentStateChange();
                            }

                            //_isCreditActive = false; 
                        }
                        else if (errorCode == NETWORK_NOLINK)
                        {
                            if (IsCreditActive == true)
                            {
                                WriteToEventLog("Network Down", EventLogEntryType.FailureAudit);
                                _isCreditActive = false;
                                OnPaymentStateChange();
                            }

                            //_isCreditActive = false;
                        }
                        else if (errorCode == NETWORK_NOTRANS)
                        {
                            if (IsCreditActive)
                            {
                                WriteToEventLog("Transaction server not responding", EventLogEntryType.FailureAudit);
                                _isCreditActive = false;
                                OnPaymentStateChange();
                            }

                           // _isCreditActive = false;
                        }

                        OnNetworkError(errorCode);

                    }
                    else if (data.Contains("R8_EqpError:"))
                    {
                        char[] sep = {':'};
                        string[] d = data.Split(sep);
                        string errorCode = String.IsNullOrEmpty(d[1]) ? "" : d[1];


                        if (errorCode == EQUIPMENT_NOERROR)
                        {
                            if (!IsCashActive)
                            {

                                WriteToEventLog("Bill Acceptor Switched to working state", EventLogEntryType.Information);
                                _isCashActive = true;
                                OnPaymentStateChange();
                            }


                        }
                        else if (errorCode == EQUIPMENT_FUNDSNR)
                        {
                            if (IsCashActive)
                            {
                                WriteToEventLog("Bill Acceptor Funds Not Reported", EventLogEntryType.FailureAudit);
                                _isCashActive = false;
                                OnPaymentStateChange();
                            }

                        }
                        else if (errorCode == EQUIPMENT_BUSY)
                        {
                            if (IsCashActive)
                            {
                                WriteToEventLog("Bill Acceptor Busy", EventLogEntryType.FailureAudit);
                                _isCashActive = false;
                                OnPaymentStateChange();
                            }

                        }
                        else if (errorCode == EQUIPMENT_BACOMM)
                        {
                            if (IsCashActive)
                            {
                                WriteToEventLog("Bill Acceptor communication error", EventLogEntryType.FailureAudit);
                                _isCashActive = false;
                                OnPaymentStateChange();
                            }

                        }
                        else if (errorCode == EQUIPMENT_BAGENERIC)
                        {
                            if (IsCashActive)
                            {
                                WriteToEventLog("Bill Acceptor generic error", EventLogEntryType.FailureAudit);
                                _isCashActive = false;
                                OnPaymentStateChange();
                            }

                        }
                        else if (errorCode == EQUIPMENT_BAJAM)
                        {
                            if (IsCashActive)
                            {
                                WriteToEventLog("Bill Acceptor jammed", EventLogEntryType.FailureAudit);
                                _isCashActive = false;
                                OnPaymentStateChange();
                            }

                        }
                        else if (errorCode == EQUIPMENT_BAFULL)
                        {
                            if (IsCashActive)
                            {
                                WriteToEventLog("Bill Acceptor Full", EventLogEntryType.FailureAudit);
                                _isCashActive = false;
                                OnPaymentStateChange();
                            }

                        }
                        else if (errorCode == EQUIPMENT_BANOVAULT)
                        {
                            if (IsCashActive)
                            {
                                WriteToEventLog("Bill Acceptor vault missing", EventLogEntryType.FailureAudit);
                                _isCashActive = false;
                                OnPaymentStateChange();
                            }

                        }

                        OnEquipmentError(errorCode);
                    }
                    else if (data.Contains("R9_"))
                    {
                        //R9_Msg(this, new CashlessEventArgs());
                    }
                    else if (data.Contains("R10_Management"))
                    {
                        OnManagement();
                    }
                    //else
                    //{
                    //    OnPaid(-1, "ERROR: " + data);
                    //}

                }
                catch (Exception ex)
                {
                    WriteToEventLog(ex.Message, EventLogEntryType.Error);
                }
 
            }
            else
            {
                WriteToEventLog("_sp_DataReceived: Port Not Open", EventLogEntryType.Error);
            }
        }

        /// <summary>
        /// Send data to the controller
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        private bool SendData(string s)
        {
            bool retval = false;

            try
            {
                _sp.WriteLine(s);

                retval = true;
            }
            catch (ArgumentNullException e)
            {
                String msg = e.Message;
                retval = false;
            }
            catch (InvalidOperationException ioe)
            {
                String msg = ioe.Message;
                retval = false;
            }

            return retval;
        }

        public bool SendMessage(CashlessSendMsg msg)
        {
            return SendMessage(msg ,null);
        }

        public bool SendMessage(CashlessSendMsg msg, String data)
        {

            bool retval = false;
            

            if (msg == CashlessSendMsg.M1_STATUS)
            {
                retval = SendData("M1_status");
            }
            else if (msg == CashlessSendMsg.M2_COLLECT)
            {
                int amt = -1;

                try
                {
                    amt = Int32.Parse(data);
                }
                catch (Exception e)
                {
                    data = "";
                }

                switch (amt)
                {
                    case 100: retval = SendData("M2_collect 01 dollars"); break;
                    case 500: retval = SendData("M2_collect 05 dollars"); break;
                    case 1000: retval = SendData("M2_collect 10 dollars"); break;
                    case 2000: retval = SendData("M2_collect 20 dollars"); break;
                    default: retval = SendData("M2_collect 20 dollars"); break;
                }
            }
            else if (msg == CashlessSendMsg.M3_RESET)
            {
                retval = SendData("M3_reset");
            }
            else if (msg == CashlessSendMsg.M4_STOP)
            {
                retval = SendData("M4_stop");
            }
            else if (msg == CashlessSendMsg.M5_VERSION)
            {
                retval = SendData("M5_version#");
            }
            else if (msg == CashlessSendMsg.M6_IP_MAC)
            {
                retval = SendData("M6_IP/MAC");
            }
            else if (msg == CashlessSendMsg.M7_MISC)
            {
                retval = SendData("M7_");
            }

            return retval;
        }

        /// <summary>
        /// Close the connection to the controller
        /// </summary>
        public void Disconnect()
        {
            try
            {
                _sp.DataReceived -= new SerialDataReceivedEventHandler(_sp_DataReceived);
                _sp.Close();
                _sp = null;
            }
            catch (Exception e)
            {
                WriteToEventLog("Disconnect(): " + e.Message, EventLogEntryType.Error);
            }
        }

        /// <summary>
        /// Generic error logger to the application event log
        /// </summary>
        /// <param name="message"></param>
        /// <param name="type"></param>
        private void WriteToEventLog(string message, EventLogEntryType type)
        {
            try
            {
                EventLog.WriteEntry("CashlessController", message, type);
            }
            catch (Exception e)
            {
                //int test = 0;
                // Just swallow exception for now - Check for log file full
                string test = e.Message;
            }
        }


        #region Properties
        /// <summary>
        /// Last message sent from kiosk
        /// </summary>
        private CashlessSendMsg CurrentSend
        {
            get { return _currentSend; }
            set { _currentSend = value; }
        }

        /// <summary>
        /// Last Received message from controller board
        /// </summary>
        private CashlessRecvMsg CurrentRecv
        {
            get { return _currentRecv; }
            set { _currentRecv = value; }
        }

        public bool IsCashActive
        {
            get { return _isCashActive; }
        }


        public bool IsCreditActive
        {
            get { return _isCreditActive; }
        }

        public int PollInterval
        {
            get { return _pollInterval; }
            set 
            { 
                _pollInterval = value;
                PollTimer.Stop();
                PollTimer.Start();            
            }
        }


        #endregion

        private void OnControllerStateChange() {

            object[] obj = new object[2];
            obj[0] = this;
            obj[1] = new CashlessEventArgs();

        }

        private void OnPaymentStateChange()
        {

            object[] obj = new object[2];
            obj[0] = this;
            obj[1] = new CashlessEventArgs();

        }

        private void OnReady()
        {
            CashlessEventArgs args = new CashlessEventArgs();

            object[] obj = new object[2];
            obj[0] = this;
            obj[1] = args;

        }

        private void OnStandby()
        {
            CashlessEventArgs args = new CashlessEventArgs();

            object[] obj = new object[2];
            obj[0] = this;
            obj[1] = args;

        }

        private void OnPaid(int amt,string trans)
        {
            if (!_isProcessingPayment)
            {
                _isProcessingPayment = true;
                WriteToEventLog("OnPaid(): " + amt + ":" + trans, EventLogEntryType.Information);                
                _paymentAmount = amt;
                _paymentTrans = trans;
                StopStatusPolling();
            }
            else
            {
                WriteToEventLog("Already processing payment", EventLogEntryType.Information);
            }

        }

        private void OnDenied()
        {

            if (!_isProcessingPayment)
            {
                _isProcessingPayment = true;
                _paymentAmount = -1;
                _paymentTrans = "DENIED";
                StopStatusPolling();
            }

        }

        private void OnIPMAC(string[] vals)
        {
            CashlessEventArgs args = new CashlessEventArgs();
            args.EventData = vals;

            object[] obj = new object[2];
            obj[0] = this;
            obj[1] = args;
        }

        private void OnEquipmentError(string error)
        {
            CashlessEventArgs args = new CashlessEventArgs();
            args.EventData = error;

            object[] obj = new object[2];
            obj[0] = this;
            obj[1] = args;
        }

        private void OnNetworkError(string error)
        {
            CashlessEventArgs args = new CashlessEventArgs();
            args.EventData = error;

            object[] obj = new object[2];
            obj[0] = this;
            obj[1] = args;

        }

        private void OnVersion(string version)
        {
            CashlessEventArgs args = new CashlessEventArgs();
            args.EventData = version;

            object[] obj = new object[2];
            obj[0] = this;
            obj[1] = args;

        }

        private void OnManagement()
        {
            CashlessEventArgs args = new CashlessEventArgs();

            object[] obj = new object[2];
            obj[0] = this;
            obj[1] = args;

        }

        public bool IsConnectionOpen()
        {
            bool retval = true;

            try
            {
                if (_sp.IsOpen)
                {
                    IsAlive = true;
                    retval = true;
                }
                else
                {
                    IsAlive = false;
                    retval = false;
                }
            }
            catch (Exception e)
            {
                retval = false;
            }

            return retval;
        }

    }



    /// <summary>
    /// EventArgs class for 
    /// </summary>
    public class CashlessEventArgs : EventArgs {

        public object EventData;
        public int Amount = 0;
        public string Transmittal = null;

        public CashlessEventArgs(object edata) {
            EventData = edata;
        }

        public CashlessEventArgs()
        {
            EventData = new object(); ;
        }
    }

}
